﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MvcWiki.Repository;
using MvcWiki.Model;
using MvcWiki.Exceptions;
using System.IO;
using System.Xml;

namespace MvcWiki.Repository.FileSystem
{
    public class WikiPageFileSystemRepository : IWikiPageRepository
    {
        IWikiRepositoryFactory RepositoryFactory;
        IUnitOfWork UnitOfWork;
        string DataPath;
        IFileSystemWrapper FileSystem;

        public WikiPageFileSystemRepository(IWikiRepositoryFactory repositoryFactory, IUnitOfWork unitOfWork, string dataPath, IFileSystemWrapper fileSystem)
        {
            this.RepositoryFactory = repositoryFactory;
            this.UnitOfWork = unitOfWork;
            this.DataPath = dataPath;
            this.FileSystem = fileSystem;
        }

        #region IUnitOfWorkPersistence

        public void InsertEntity(Entity entity)
        {
            var wikiPage = entity as WikiPage;

            var wikiNamespaceDirectoryNames = FileSystem.GetDirectories(DataPath, wikiPage.Namespace.Name + "---*");

            if (wikiNamespaceDirectoryNames.Length == 0)
            {
                throw new NamespaceDoesNotExistException();
            }

            var wikiNamespaceDirectoryName = wikiNamespaceDirectoryNames[0];

            var wikiPageDirectoryNames = FileSystem.GetDirectories(wikiNamespaceDirectoryName, wikiPage.Name + "---*");

            if (wikiPageDirectoryNames.Length > 0)
            {
                throw new PageAlreadyExistsException();
            }

            var wikiPageDirectoryName = Path.Combine(wikiNamespaceDirectoryName, wikiPage.Name + "---" + wikiPage.Id.ToString());

            FileSystem.CreateDirectory(wikiPageDirectoryName);

            foreach(var revision in wikiPage.Revisions ?? new List<WikiPageRevision>())
            {
                var revisionFileName = String.Format("r---{0:yyyyMMddHHmmss}.txt", revision.Created);
                var revisionFullFileName = Path.Combine(wikiPageDirectoryName, revisionFileName);

                FileSystem.WriteToFile(revisionFullFileName, revision.Content);
            }
        }

        public void UpdateEntity(Entity entity)
        {
            var wikiPage = entity as WikiPage;

            var wikiNamespaceDirectoryNames = FileSystem.GetDirectories(DataPath, wikiPage.Namespace.Name + "---*");

            if (wikiNamespaceDirectoryNames.Length == 0)
            {
                throw new NamespaceDoesNotExistException();
            }

            var wikiNamespaceDirectoryName = wikiNamespaceDirectoryNames[0];

            var wikiPageDirectoryNames = FileSystem.GetDirectories(wikiNamespaceDirectoryName, "*---" + wikiPage.Id);

            if (wikiPageDirectoryNames.Length == 0)
            {
                throw new PageDoesNotExistException();
            }

            var wikiPageDirectoryName = wikiPageDirectoryNames[0];

            var oldWikiPageName = wikiPageDirectoryName.Remove(wikiPageDirectoryName.LastIndexOf("---")).Substring(wikiPageDirectoryName.LastIndexOf('\\') + 1);

            if (wikiPage.Name != oldWikiPageName)
            {
                var newWikiPageDirectoryName = Path.Combine(wikiNamespaceDirectoryName, wikiPage.Name + "---" + wikiPage.Id.ToString());

                FileSystem.MoveDirectory(wikiPageDirectoryName, newWikiPageDirectoryName);

                wikiPageDirectoryName = newWikiPageDirectoryName;
            }

            var wikiPageRevisionFileNames = FileSystem.GetFiles(wikiPageDirectoryName, "r---*.txt");

            foreach (var wikiPageRevisionFileName in wikiPageRevisionFileNames)
            {
                var wikiPageRevisionCreatedStr = wikiPageRevisionFileName.Remove(wikiPageRevisionFileName.LastIndexOf(".txt")).Substring(wikiPageRevisionFileName.LastIndexOf("r---") + 4);
                var wikiPageRevisionCreated = new DateTime(
                    int.Parse(wikiPageRevisionCreatedStr.Substring(0, 4)),
                    int.Parse(wikiPageRevisionCreatedStr.Substring(4, 2)),
                    int.Parse(wikiPageRevisionCreatedStr.Substring(6, 2)),

                    int.Parse(wikiPageRevisionCreatedStr.Substring(8, 2)),
                    int.Parse(wikiPageRevisionCreatedStr.Substring(10, 2)),
                    int.Parse(wikiPageRevisionCreatedStr.Substring(12, 2)),
                    DateTimeKind.Utc);

                if(!wikiPage.Revisions.Any(x => x.Created == wikiPageRevisionCreated))
                {
                    FileSystem.DeleteFile(wikiPageRevisionFileName);
                }
            }

            foreach (var revision in wikiPage.Revisions ?? new List<WikiPageRevision>())
            {
                var revisionFileName = String.Format("r---{0:yyyyMMddHHmmss}.txt", revision.Created);
                var revisionFullFileName = Path.Combine(wikiPageDirectoryName, revisionFileName);

                FileSystem.WriteToFile(revisionFullFileName, revision.Content);
            }
        }

        public void DeleteEntity(Entity entity)
        {
            var wikiPage = entity as WikiPage;

            var wikiNamespaceDirectoryNames = FileSystem.GetDirectories(DataPath, wikiPage.Namespace.Name + "---*");

            if (wikiNamespaceDirectoryNames.Length == 0)
            {
                throw new NamespaceDoesNotExistException();
            }

            var wikiNamespaceDirectoryName = wikiNamespaceDirectoryNames[0];

            var wikiPageDirectoryNames = FileSystem.GetDirectories(wikiNamespaceDirectoryName, "*---" + wikiPage.Id);

            if (wikiPageDirectoryNames.Length == 0)
            {
                throw new PageDoesNotExistException();
            }

            var wikiPageDirectoryName = wikiPageDirectoryNames[0];

            FileSystem.DeleteDirectory(wikiPageDirectoryName);
        }

        #endregion

        public IList<WikiPage> GetAllWikiPages(string wikiNamespaceName)
        {
            var wikiNamespace = RepositoryFactory.GetWikiNamespaceRepository().GetWikiNamespace(wikiNamespaceName);

            var wikiNamespaceDirectoryNames = FileSystem.GetDirectories(DataPath, wikiNamespaceName + "---*");

            if (wikiNamespaceDirectoryNames.Length == 0)
            {
                throw new NamespaceDoesNotExistException();
            }

            var wikiNamespaceDirectoryName = wikiNamespaceDirectoryNames[0];

            var wikiPageDirectoryNames = FileSystem.GetDirectories(wikiNamespaceDirectoryName, "*---*");

            var wikiPages = new List<WikiPage>();

            foreach (var wikiPageDirectoryName in wikiPageDirectoryNames)
            {
                var wikiPage = new WikiPage { Namespace = wikiNamespace };
                wikiPage.Id = new Guid(wikiPageDirectoryName.Substring(wikiPageDirectoryName.LastIndexOf("---") + 3));
                wikiPage.Name = wikiPageDirectoryName.Remove(wikiPageDirectoryName.LastIndexOf("---")).Substring(wikiPageDirectoryName.LastIndexOf('\\') + 1);
                wikiPage.Revisions = new List<WikiPageRevision>();
                
                var wikiPageRevisionFileNames = FileSystem.GetFiles(wikiPageDirectoryName, "r---*.txt");

                Func<string, string> FileName = x => x.Substring(x.LastIndexOf('\\') + 1);

                Func<string, DateTime> CreatedFromFileName = x => new DateTime(
                    int.Parse(FileName(x).Substring(4, 4)), int.Parse(FileName(x).Substring(8, 2)), int.Parse(FileName(x).Substring(10, 2)),
                    int.Parse(FileName(x).Substring(12, 2)), int.Parse(FileName(x).Substring(14, 2)), int.Parse(FileName(x).Substring(16, 2)),
                    DateTimeKind.Utc);

                wikiPage.Revisions = wikiPageRevisionFileNames.OrderBy(x => x).Select(
                    x => new WikiPageRevision(CreatedFromFileName(x), FileSystem.ReadFromFile(x))
                    ).ToList();

                wikiPages.Add(wikiPage);
            }

            return wikiPages;
        }

        public WikiPage GetWikiPage(string wikiNamespaceName, string wikiPageName)
        {
            var wikiPage = new WikiPage
            {
                Name = wikiPageName,
                Namespace = RepositoryFactory.GetWikiNamespaceRepository().GetWikiNamespace(wikiNamespaceName)
            };

            var wikiNamespaceDirNames = FileSystem.GetDirectories(DataPath, wikiNamespaceName + "---*");

            if (wikiNamespaceDirNames.Length == 0)
            {
                throw new PageDoesNotExistException();
            }

            var wikiPageDirNames = FileSystem.GetDirectories(wikiNamespaceDirNames[0], wikiPageName + "---*");

            if (wikiPageDirNames.Length == 0)
            {
                throw new PageDoesNotExistException();
            }

            var wikiPageDirName = wikiPageDirNames[0];

            wikiPage.Id = new Guid(wikiPageDirName.Substring(wikiPageDirName.LastIndexOf("---") + 3));

            var wikiPageRevisionFileNames = FileSystem.GetFiles(wikiPageDirName, "r---*.txt");

            Func<string, string> FileName = x => x.Substring(x.LastIndexOf('\\') + 1);

            Func<string, DateTime> CreatedFromFileName = x => new DateTime(
                int.Parse(FileName(x).Substring(4, 4)), int.Parse(FileName(x).Substring(8, 2)), int.Parse(FileName(x).Substring(10, 2)),
                int.Parse(FileName(x).Substring(12, 2)), int.Parse(FileName(x).Substring(14, 2)), int.Parse(FileName(x).Substring(16, 2)),
                DateTimeKind.Utc);

            wikiPage.Revisions = wikiPageRevisionFileNames.OrderBy(x => x).Select(
                x => new WikiPageRevision(CreatedFromFileName(x), FileSystem.ReadFromFile(x))
                ).ToList();

            UnitOfWork.Selected(wikiPage, this);

            return wikiPage;
        }

        public void SaveWikiPage(WikiPage wikiPage)
        {
            RepositoryFactory.GetWikiNamespaceRepository().SaveWikiNamespace(wikiPage.Namespace);

            UnitOfWork.Saved(wikiPage, this);
        }

        public void DeleteWikiPage(WikiPage wikiPage)
        {
            UnitOfWork.Deleted(wikiPage, this);
        }
    }
}
